/*
 * File:   driver_rtc.c
 * Author: Ich
 *
 * Created on 23.02.2014, 16:03
 *
 */



#include <plib.h>
#include "driver_rtc.h"
#include "driver_i2c.h"

#include "util/util_conv.h"

/*******************************************************************************
 *  private preprocessing definitions
 */

/*
 * RTC Registers
 */
#define DRV_RTC_REG_RTCSEC              (0x00)  // 
#define DRV_RTC_REG_RTCMIN              (0x01)  //
#define DRV_RTC_REG_RTCHOUR             (0x02)  //
#define DRV_RTC_REG_RTCWKDAY            (0x03)  //
#define DRV_RTC_REG_RTCDATE             (0x04)  //
#define DRV_RTC_REG_RTCMTH              (0x05)  //
#define DRV_RTC_REG_RTCYEAR             (0x06)  //
#define DRV_RTC_REG_CONTROL             (0x07)  //
#define DRV_RTC_REG_OSCTRIM             (0x08)  //

#define DRV_RTC_REG_ALM0SEC             (0x0A)  //
#define DRV_RTC_REG_ALM0MIN             (0x0B)  //
#define DRV_RTC_REG_ALM0HOUR            (0x0C)  //
#define DRV_RTC_REG_ALM0WKDAY           (0x0D)  //
#define DRV_RTC_REG_ALM0DATE            (0x0E)  //
#define DRV_RTC_REG_ALM0MTH             (0x0F)  //

/*
 * Register values
 */
#define  PM                0x20       //  post-meridian bit (ADDR_HOUR)
#define  OUT_PIN           0x80       //  = b7 (ADDR_CTRL)
#define  SQWE              0x40       //  = b6. (ADDR_CTRL)
#define  EXT_OSC_EN        0x08       //  = b3. enable external oscillator (ADDR_CTRL)
#define  ALM_NO            0x00       //  no alarm activated        (ADDR_CTRL)
#define  ALM_0             0x10       //  ALARM0 is       activated (ADDR_CTRL)
#define  ALM_1             0x20       //  ALARM1 is       activated (ADDR_CTRL)
#define  ALM_01            0x30       //  both alarms are activated (ADDR_CTRL)
#define  MFP_01H           0x00       //  MFP = SQVAW(01 HERZ)      (ADDR_CTRL)
#define  MFP_04K           0x01       //  MFP = SQVAW(04 KHZ)       (ADDR_CTRL)
#define  MFP_08K           0x02       //  MFP = SQVAW(08 KHZ)       (ADDR_CTRL)
#define  MFP_32K           0x03       //  MFP = SQVAW(32 KHZ)       (ADDR_CTRL)
#define  MFP_64H           0x04       //  MFP = SQVAW(64 HERZ)      (ADDR_CTRL)
#define  ALMx_POL          0x80       //  polarity of MFP on alarm  (ADDR_ALMxCTL)
#define  ALMxC_SEC         0x00       //  ALARM compare on SEC      (ADDR_ALMxCTL)
#define  ALMxC_MIN         0x10       //  ALARM compare on MIN      (ADDR_ALMxCTL)
#define  ALMxC_HR          0x20       //  ALARM compare on HOUR     (ADDR_ALMxCTL)
#define  ALMxC_DAY         0x30       //  ALARM compare on DAY      (ADDR_ALMxCTL)
#define  ALMxC_DAT         0x40       //  ALARM compare on DATE     (ADDR_ALMxCTL)
#define  ALMxC_ALL         0x70       //  ALARM compare on all param(ADDR_ALMxCTL)
#define  ALMx_IF           0x08       //  MASK of the ALARM_IF      (ADDR_ALMxCTL)
#define  OSCON             0x20       //  state of the oscillator(running or not)
#define  VBATEN            0x08       //  enable battery for back-up
#define  SQVAW    PORTAbits.RA4       //  square wave MFP from RTCC
#define  START_32KHZ       0x80       //  start crystal: ST = b7 (ADDR_SEC)
#define  LP                0x20       //  mask for the leap year bit(MONTH REG)
#define  HOUR_12           0x40       //  12 hours format   (ADDR_HOUR)


#define DRV_RTC_I2CADDR         (0x6F)              // 0b1101111x: address of module
#define DRV_RTC_I2CMODULE       (I2C_MODULE)(I2C1)  // i2c module
#define DRV_RTC_I2CSPEED        (50000)            // speed of I2C-Module

 /*******************************************************************************
 *  private variables
 */
I2C_7_BIT_ADDRESS DRV_RTC_ADDRESS;

/*******************************************************************************
 *  private functions
 */


/* Interrupt function. This function will be
 * called if RTC-module generates an interrupt.
 *
 */
void  (*drvRtc_interruptFunction)(void);



/* if minute alarm appears, the alarm for the next minute
 * can set by this function.
 *
 *
 */
BOOL drvRtc_renewMinuteAlarm          ( void )
{
    // read actual minute
    UINT8 minute;
    BOOL successful = drvI2C_readByte(   DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCMIN, &minute);

    // calc next minute
    utilConv_bcd2dec(minute,&minute);
    if(++minute > 60)
    {
        minute = 0;
    }
    utilConv_dec2bcd(minute,&minute);


    // set next alarm minute
    if(successful)
    {
        successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_ALM0MIN, minute);
    }
    return successful;
}

 /*******************************************************************************
 *  public variables (defined by external)
 */


/*******************************************************************************
 *  public functions
 */

/* init RTC timer */
void drvRtc_init(void (*interruptFunction)(void))
{
    INTDisableInterrupts();
    // init RTC-Alarm Input
    // set RD0 as input
    mPORTDSetPinsDigitalIn(BIT_0);
    // config interrupt mode
    ConfigINT0(EXT_INT_PRI_4 | RISING_EDGE_INT | EXT_INT_ENABLE);
    drvRtc_interruptFunction = interruptFunction;
    INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR);

    // init I2C Module
    drvI2C_init(DRV_RTC_I2CMODULE,DRV_RTC_I2CSPEED);
    I2C_FORMAT_7_BIT_ADDRESS(
            DRV_RTC_ADDRESS,
            DRV_RTC_I2CADDR,
            I2C_WRITE);

    // external oscillator disable (use ext. quartz)
    UINT8 regValue = 0;
    BOOL successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_CONTROL, regValue);

    // read day + OSCON bit
    if(successful)
    {
        regValue = 0;
        successful = drvI2C_readByte(  DRV_RTC_I2CMODULE,DRV_RTC_ADDRESS,
                                       DRV_RTC_REG_RTCWKDAY, &regValue);
    }

    // enable the battery back-up
    if(successful)
    {
        regValue = regValue | VBATEN;
        successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCWKDAY, regValue);
    }

    // start quartz if not running
    regValue &= OSCON;
    if( successful && (regValue != OSCON))
    {
        // oscillator not running --> set default date/time
        // default time: 13:00:00
        successful = drvRtc_setTime(0x13,0x00,0x00);
        if( successful )
        {
            // default date: 20. April. 2014
            successful = drvRtc_setDate(0x14,0x04,0x20);
        }
    }

    drvRtc_alarmOff();
    drvRtc_setMinuteAlarm();



    INTEnableInterrupts();
}


BOOL drvRtc_setDate(UINT8 year, UINT8 month, UINT8 day)
{
    BOOL successful = drvI2C_writeByte( DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCYEAR, year);
    if(successful)
    {
        successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCMTH, month);
    }
    if(successful)
    {
        successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCDATE, day);
    }

    return successful;
}


BOOL drvRtc_setTime(UINT8 hour, UINT8 min, UINT8 sec)
{
    // time set is always in 24h mode
    BOOL successful = drvI2C_writeByte( DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCHOUR, hour);
    if(successful)
    {
        successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCMIN, min);
    }
    if(successful)
    {
        successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCSEC, sec | START_32KHZ);
    }

    return successful;
}


BOOL drvRtc_getDate(UINT8 *pYear, UINT8 *pMonth, UINT8 *pDay)
{
    BOOL successful = drvI2C_readByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCYEAR, pYear);
    if(successful)
    {
        successful = drvI2C_readByte(   DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCMTH, pMonth);
        *pMonth = (*pMonth) & 0x1F; // mask month values
    }
    if(successful)
    {
        successful = drvI2C_readByte(   DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCDATE, pDay);
    }

    return successful;
}


BOOL drvRtc_getTime(UINT8 *pHour, UINT8 *pMin, UINT8 *pSec)
{
    BOOL successful = drvI2C_readByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCHOUR, pHour);
    *pHour = (*pHour) & 0x1F; // mask hour values
    if(successful)
    {
        successful = drvI2C_readByte(   DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCMIN, pMin);
    }
    if(successful)
    {
        successful = drvI2C_readByte(   DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_RTCSEC, pSec);
        *pSec = (*pSec) & 0x7F; // mask seconds values
    }

    return successful;
}

BOOL drvRtc_alarmOff(void)
{
    UINT8 regValue = 0;
    BOOL successful = drvI2C_writeByte( DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_CONTROL, regValue);

    return successful;
}


BOOL drvRtc_setMinuteAlarm          ( void )
{

    BOOL successful = drvRtc_renewMinuteAlarm();

    // config alarm with 1 alarm per minute
    UINT8 regValue;
    if(successful)
    {
        regValue = ALMx_POL | ALMxC_MIN | ALMxC_SEC;
        successful = drvI2C_writeByte( DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_ALM0WKDAY, regValue);
    }
                                    
    // activate alarm #0 on output pin
    if(successful)
    {
        successful = drvI2C_readByte(   DRV_RTC_I2CMODULE,DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_CONTROL, &regValue);
    }
    if(successful)
    {
        regValue |= (ALM_0 | OUT_PIN );
        successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_CONTROL, regValue);
    }
    if(successful)
    {
        successful = drvRtc_clearAlarmFlag();
    }
    return successful;
}


BOOL drvRtc_clearAlarmFlag          ( void )
{
    UINT8 regValue;
    BOOL successful = drvI2C_readByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_ALM0WKDAY, &regValue);

    while((regValue & ALMx_IF) == ALMx_IF && successful == TRUE)
    {
        // clear alarm flag
        if(successful)
        {
            regValue &= ~(ALMx_IF); 
            successful = drvI2C_writeByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                            DRV_RTC_REG_ALM0WKDAY, regValue);
        }

        // read alarm flag
        if(successful)
        {
            successful = drvI2C_readByte(  DRV_RTC_I2CMODULE, DRV_RTC_ADDRESS,
                                        DRV_RTC_REG_ALM0WKDAY, &regValue);
        }
    }
    return successful;
}

// interrupt handler for alarm functions
void __ISR(_EXTERNAL_0_VECTOR, ipl4) INT0Handler(void)
{
    drvRtc_renewMinuteAlarm();
    drvRtc_clearAlarmFlag();

    mINT0ClearIntFlag();
    if(drvRtc_interruptFunction != NULL)
    {
        drvRtc_interruptFunction();
    }


}